/***********************************************************************
 *
 * $CVSHeader$
 *
 * This file is part of WebScarab, an Open Web Application Security
 * Project utility. For details, please see http://www.owasp.org/
 *
 * Copyright (c) 2002 - 2004 Rogan Dawes
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Getting Source
 * ==============
 *
 * Source for this application is maintained at Sourceforge.net, a
 * repository for free software projects.
 *
 * For details, please see http://www.sourceforge.net/projects/owasp
 *
 */

/*
 * ProxyPanel.java
 *
 * Created on July 25, 2003, 11:07 PM
 */

package org.owasp.webscarab.plugin.proxy.swing;

import org.owasp.webscarab.model.ConversationID;
import org.owasp.webscarab.model.HttpUrl;

import org.owasp.webscarab.ui.swing.SwingPluginUI;

import org.owasp.webscarab.plugin.proxy.ListenerSpec;
import org.owasp.webscarab.plugin.proxy.Proxy;
import org.owasp.webscarab.plugin.proxy.ProxyUI;
import org.owasp.webscarab.util.swing.ColumnDataModel;
import org.owasp.webscarab.util.W32WinInet;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;

import java.util.logging.Logger;

import javax.swing.ListSelectionModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import java.util.Timer;
import java.util.TimerTask;

/**
 *
 * @author  rdawes
 */
public class ProxyPanel extends javax.swing.JPanel implements SwingPluginUI, ProxyUI {
    
    /**
	 * 
	 */
	private static final long serialVersionUID = -3557927604958599392L;
	private Proxy _proxy;
    private ListenerTableModel _ltm;
    private MessageTableModel _mtm;
    
    private ArrayList<ProxyPluginUI> _plugins;
    private ProxyPluginUI[] _pluginArray = new ProxyPluginUI[0];
    
    private Logger _logger = Logger.getLogger(getClass().getName());
    
    /** Creates new form ProxyPanel */
    public ProxyPanel(Proxy proxy) {
        initComponents();
        
        _proxy = proxy;
        
        _ltm = new ListenerTableModel(_proxy);
        listenerTable.setModel(_ltm);
        listenerTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        
        _mtm = new MessageTableModel();
        messageTable.setModel(_mtm);
        
        int[][] columnWidths = {
            {200, 200, 200}, 
            {50, 50, 50},
            {50, 50, 50},
            {250, 250, 250},
        };
        
        javax.swing.table.TableColumnModel columnModel = messageTable.getColumnModel();
        for (int i=0; i<columnWidths.length; i++) {
            columnModel.getColumn(i).setMinWidth(columnWidths[i][0]);
            columnModel.getColumn(i).setMaxWidth(columnWidths[i][1]);
            columnModel.getColumn(i).setPreferredWidth(columnWidths[i][2]);
        }
        
        networkComboBox.setModel(new DefaultComboBoxModel(new String[] { "Unlimited" }));
        networkComboBox.setSelectedItem("Unlimited");
        
        ListenerSpec[] proxies = _proxy.getProxies();
        for (int i=0; i<proxies.length; i++) _ltm.proxyAdded(proxies[i]);
        
        proxy.setUI(this);
        
        if (!W32WinInet.isAvailable()) {
            primaryLabel.setEnabled(false);
            primaryCheckBox.setEnabled(false);
        }
    }
    
    public javax.swing.JPanel getPanel() {
        return this;
    }
    
    public String getPluginName() {
        return new String("Proxy");
    }
    
    public void addPlugin(ProxyPluginUI plugin) {
        if (_plugins == null) {
            _plugins = new ArrayList<ProxyPluginUI>();
        }
        _plugins.add(plugin);
        _pluginArray = _plugins.toArray(_pluginArray);
        mainTabbedPane.add(plugin.getPanel(), plugin.getPluginName());
        if (plugin instanceof ManualEditPanel)
            mainTabbedPane.setSelectedIndex(mainTabbedPane.getTabCount()-1);
    }
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        mainTabbedPane = new javax.swing.JTabbedPane();
        jSplitPane1 = new javax.swing.JSplitPane();
        jPanel1 = new javax.swing.JPanel();
        jPanel2 = new javax.swing.JPanel();
        stopButton = new javax.swing.JButton();
        startButton = new javax.swing.JButton();
        jScrollPane1 = new javax.swing.JScrollPane();
        listenerTable = new javax.swing.JTable();
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        jLabel3 = new javax.swing.JLabel();
        addressTextField = new javax.swing.JTextField();
        portTextField = new javax.swing.JTextField();
        baseTextField = new javax.swing.JTextField();
        jLabel5 = new javax.swing.JLabel();
        networkComboBox = new javax.swing.JComboBox();
        primaryLabel = new javax.swing.JLabel();
        primaryCheckBox = new javax.swing.JCheckBox();
        jPanel3 = new javax.swing.JPanel();
        jScrollPane2 = new javax.swing.JScrollPane();
        messageTable = new javax.swing.JTable();

        setLayout(new java.awt.GridBagLayout());

        jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
        jSplitPane1.setResizeWeight(0.9);
        jPanel1.setLayout(new java.awt.GridBagLayout());

        jPanel2.setLayout(new java.awt.GridBagLayout());

        stopButton.setText("Stop");
        stopButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                stopButtonActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
        gridBagConstraints.weighty = 1.0;
        jPanel2.add(stopButton, gridBagConstraints);

        startButton.setText("Start");
        startButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                startButtonActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
        gridBagConstraints.weighty = 1.0;
        jPanel2.add(startButton, gridBagConstraints);

        listenerTable.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {

            },
            new String [] {

            }
        ));
        jScrollPane1.setViewportView(listenerTable);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridheight = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        jPanel2.add(jScrollPane1, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        jPanel1.add(jPanel2, gridBagConstraints);

        jLabel1.setFont(new java.awt.Font("Dialog", 0, 12));
        jLabel1.setText("Address");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPanel1.add(jLabel1, gridBagConstraints);

        jLabel2.setFont(new java.awt.Font("Dialog", 0, 12));
        jLabel2.setText("Port");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPanel1.add(jLabel2, gridBagConstraints);

        jLabel3.setFont(new java.awt.Font("Dialog", 0, 12));
        jLabel3.setText("Base URL");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPanel1.add(jLabel3, gridBagConstraints);

        addressTextField.setToolTipText("Blank or \"*\" means all interfaces");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 1.0;
        jPanel1.add(addressTextField, gridBagConstraints);

        portTextField.setToolTipText("integer between 0 and 65536");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 0.5;
        jPanel1.add(portTextField, gridBagConstraints);

        baseTextField.setToolTipText("Blank for a conventional proxy, or http://host:port for a reverse proxy.");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 1.0;
        jPanel1.add(baseTextField, gridBagConstraints);

        jLabel5.setFont(new java.awt.Font("Dialog", 0, 12));
        jLabel5.setText("Speed");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPanel1.add(jLabel5, gridBagConstraints);

        networkComboBox.setFont(new java.awt.Font("Dialog", 0, 12));
        networkComboBox.setMinimumSize(new java.awt.Dimension(32, 19));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 1.0;
        jPanel1.add(networkComboBox, gridBagConstraints);

        primaryLabel.setFont(new java.awt.Font("Dialog", 0, 12));
        primaryLabel.setText("Primary?");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 5;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 2, 0, 0);
        jPanel1.add(primaryLabel, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 5;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 0.2;
        gridBagConstraints.insets = new java.awt.Insets(0, 2, 0, 0);
        jPanel1.add(primaryCheckBox, gridBagConstraints);

        jSplitPane1.setLeftComponent(jPanel1);

        jPanel3.setLayout(new java.awt.BorderLayout());

        messageTable.setBackground(new java.awt.Color(204, 204, 204));
        messageTable.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {

            },
            new String [] {
                "Time", "ID", "Method", "URL", "Status"
            }
        ));
        messageTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_LAST_COLUMN);
        messageTable.setShowHorizontalLines(false);
        messageTable.setShowVerticalLines(false);
        jScrollPane2.setViewportView(messageTable);

        jPanel3.add(jScrollPane2, java.awt.BorderLayout.CENTER);

        jSplitPane1.setRightComponent(jPanel3);

        mainTabbedPane.addTab("Listeners", jSplitPane1);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        add(mainTabbedPane, gridBagConstraints);

    }// </editor-fold>//GEN-END:initComponents
    
    private void startButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_startButtonActionPerformed
        try {
            String address = addressTextField.getText();
            int port = Integer.parseInt(portTextField.getText().trim());
            HttpUrl base = null;
            if (!baseTextField.getText().equals("")) {
                base = new HttpUrl(baseTextField.getText());
            }
            boolean primary = primaryCheckBox.isSelected();
            ListenerSpec spec = new ListenerSpec(address, port, base, primary);
            spec.verifyAvailable();
            _proxy.addListener(spec);
            addressTextField.setText("");
            portTextField.setText("");
            baseTextField.setText("");
        } catch (Exception e) {
            _logger.severe("Error starting the listener : " + e);
            JOptionPane.showMessageDialog(null, new String[] {"Error starting proxy listener: ", e.toString()}, "Error", JOptionPane.ERROR_MESSAGE);
        }
    }//GEN-LAST:event_startButtonActionPerformed
    
    private void stopButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_stopButtonActionPerformed
        int row = listenerTable.getSelectedRow();
        if (row<0) return;
        ListenerSpec spec = _ltm.getListener(row);
        if (!_proxy.removeListener(spec)) {
            _logger.severe("Failed to stop " + spec);
        } else {
            addressTextField.setText(spec.getAddress());
            portTextField.setText(Integer.toString(spec.getPort()));
            baseTextField.setText(spec.getBase() == null ? "" : spec.getBase().toString());
            primaryCheckBox.setSelected(spec.isPrimaryProxy());
        }
    }//GEN-LAST:event_stopButtonActionPerformed
    
    public javax.swing.Action[] getConversationActions() {
        return null;
    }
    
    public javax.swing.Action[] getUrlActions() {
        return null;
    }
    
    public void proxyAdded(final ListenerSpec spec) {
        if (SwingUtilities.isEventDispatchThread()) {
            _ltm.proxyAdded(spec);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    proxyAdded(spec);
                }
            });
        }
    }
    
    public void proxyRemoved(final ListenerSpec spec) {
        if (SwingUtilities.isEventDispatchThread()) {
            _ltm.proxyRemoved(spec);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    proxyRemoved(spec);
                }
            });
        }
    }
    
    public void proxyStarted(ListenerSpec spec) {
    }
    
    public void proxyStartError(final ListenerSpec spec, final IOException ioe) {
        if (SwingUtilities.isEventDispatchThread()) {
            JOptionPane.showMessageDialog(this, new String[] {"Error starting proxy listener: ", spec.toString(), ioe.toString()}, "Error", JOptionPane.ERROR_MESSAGE);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    proxyStartError(spec, ioe);
                }
            });
        }

    }
    
    public void proxyStopped(ListenerSpec spec) {
    }
    
    public void requested(final ConversationID id, final String method, final HttpUrl url) {
        if (SwingUtilities.isEventDispatchThread()) {
            _mtm.addRow(id, method, url);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    requested(id, method, url);
                }
            });
        }
    }
    
    public void received(final ConversationID id, final String status) {
        if (SwingUtilities.isEventDispatchThread()) {
            _mtm.updateRow(id, status);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    received(id, status);
                }
            });
        }
    }
    
    public void aborted(final ConversationID id, final String reason) {
        if (SwingUtilities.isEventDispatchThread()) {
            _mtm.updateRow(id, reason);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    aborted(id, reason);
                }
            });
        }
    }
    
    public void setEnabled(final boolean enabled) {
        if (SwingUtilities.isEventDispatchThread()) {
            startButton.setEnabled(enabled);
            stopButton.setEnabled(enabled);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    setEnabled(enabled);
                }
            });
        }
    }
    
    public ColumnDataModel[] getConversationColumns() {
        return null;
    }
    
    public ColumnDataModel[] getUrlColumns() {
        return null;
    }
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JTextField addressTextField;
    private javax.swing.JTextField baseTextField;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel5;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JPanel jPanel3;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JSplitPane jSplitPane1;
    private javax.swing.JTable listenerTable;
    private javax.swing.JTabbedPane mainTabbedPane;
    private javax.swing.JTable messageTable;
    private javax.swing.JComboBox networkComboBox;
    private javax.swing.JTextField portTextField;
    private javax.swing.JCheckBox primaryCheckBox;
    private javax.swing.JLabel primaryLabel;
    private javax.swing.JButton startButton;
    private javax.swing.JButton stopButton;
    // End of variables declaration//GEN-END:variables
    
    private class MessageTableModel extends AbstractTableModel {
        
        /**
		 * 
		 */
		private static final long serialVersionUID = 3090029861017879230L;
		private ArrayList<Object[]> _rows = new ArrayList<Object[]>();
        private Timer _timer = new Timer(true);
        
        private String[] _columns = new String [] {
            "Time", "ID", "Method", "URL", "Status"
        };
        
        public String getColumnName(int columnIndex) {
            return _columns[columnIndex];
        }
        
        public int getColumnCount() {
            return _columns.length;
        }
        
        public int getRowCount() {
            return _rows.size();
        }
        
        public Object getValueAt(int rowIndex, int columnIndex) {
            Object[] row = _rows.get(rowIndex);
            return row[columnIndex];
        }
        
        public void addRow(ConversationID id, String method, HttpUrl url) {
            Object[] row = new Object[] {new Date(), id, method, url, null};
            _rows.add(row);
            fireTableRowsInserted(_rows.size()-1, _rows.size()-1);
        }
        
        public void updateRow(final ConversationID id, String status) {
            for (int i=0; i<_rows.size(); i++) {
                Object[] row = (Object[]) _rows.get(i);
                if (row[1].equals(id)) {
                    row[4] = status;
                    fireTableCellUpdated(i, 4);
                    _timer.schedule(new TimerTask() {
                        public void run() {
                            removeRow(id);
                        }
                    }, 5000);
                    return;
                }
            }
        }
        
        public void removeRow(final ConversationID id) {
            if (SwingUtilities.isEventDispatchThread()) {
                for (int i=0; i<_rows.size(); i++) {
                    Object[] row = (Object[]) _rows.get(i);
                    if (row[1].equals(id)) {
                        _rows.remove(i);
                        fireTableRowsDeleted(i, i);
                        return;
                    }
                }
            } else {
                SwingUtilities.invokeLater(new Runnable() { 
                    public void run() {
                        removeRow(id);
                    }
                });
            }
        }
        
    }
    
}
